package com.thought.wxsession.task;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.thought.wxsession.config.WxCpPropertiesConstantsUtils;
import com.thought.wxsession.cons.MsgAction;
import com.thought.wxsession.data.ChatMsg;
import com.thought.wxsession.data.Chatdata;
import com.thought.wxsession.data.WxChatResp;
import com.thought.wxsession.entity.WxMsg;
import com.thought.wxsession.service.IWxMsgService;
import com.thought.wxsession.utils.RSAEncrypt;
import com.tencent.wework.Finance;
import com.thought.wxsession.utils.RabbitMQUtil;
import com.thought.wxsession.utils.SessionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import static com.thought.wxsession.cons.MqCons.WX_MEAAGES_HANDEL_EXCHANGE;
import static com.thought.wxsession.cons.MqCons.WX_MEAAGES_HANDEL_KEY;
import static com.thought.wxsession.cons.MsgAction.SWITCH;
import static com.thought.wxsession.cons.MsgStatus.FAIL;
import static com.thought.wxsession.cons.MsgStatus.SUCCESS;

/**
 * 拉取会话消息
 * @Author：zhuqinyue
 * @Package：com.thought.wxsession.task
 * @Project：session-save
 * @name：WxMessageTask
 * @Date：2022/12/25 17:01
 * @Filename：WxMessageTask
 */
@Component
@Slf4j
public class WxMessageTask {

    @Autowired
    IWxMsgService wxMsgService;

    @Autowired
    private RabbitMQUtil rabbitMQUtil;



    public void run() {
        {


            //使用sdk前需要初始化，初始化成功后的sdk可以一直使用。
            //如需并发调用sdk，建议每个线程持有一个sdk实例。
            log.info("加载企业微信sdk开始");
            long sdk = Finance.NewSdk();
            log.info("创建企业微信sdk成功:{}", sdk);
            // 企业id
            String corpid = WxCpPropertiesConstantsUtils.Wxcp_corpId;
            // 管理凭证密钥 配置完公钥后 可以获取
            String secret = WxCpPropertiesConstantsUtils.Wxcp_secret;
            // 私钥，与公钥为一对
            String priKey = "-----BEGIN RSA PRIVATE KEY-----\n" +
                    "MIIEowIBAAKCAQEA1Ivjw1NQELGYxVYmXxwsDBiGR+vgJXrIPKu6IVGKU2JGR3aT\n" +
                    "wp2lsGXNn2OxDRiLAbOhqMfvhHD54kH+kjXhx62qNQ0W7okGuprCj0NZWHcSJZvc\n" +
                    "WHpUGNQSLY0jABpV/N0FxB3ECYlqY5cxVUReMmAxkl6qlYMZ1bbu+6D49MU/f7eL\n" +
                    "N4UqI0MQpF2blNyUFmkVe7F8/SoYWEqmPuEA6vg34JqPPdxnsGBnOJjvymhpb78G\n" +
                    "tNLXA0rploWjGnhPz8GKYScQOyPvLfR81k91GHQYHPcTml0pxtE29icRvdkRMQDM\n" +
                    "DNrIu52s/IBVVrK3je5nFxSIqM0yGJTl3i/ChQIDAQABAoIBABxTYFFBoyP2ZyO1\n" +
                    "bXuzZ3NdOVgQaVjVWiYrxKx4D3U5XaBcfyiqldBCYPri8E+Me8oIRH0X2CXQYJz0\n" +
                    "qpaVs7dGv64+RlYX0NvBkDhDg360r6hvcxgfhWGhsaJ8crOqztbaJdCTHvMeax1G\n" +
                    "ov54FA2moDSfhSBTznea35KNS2BjQuySd6wUyC4qt9eC5nu6A7NZPhFArG3GvCBR\n" +
                    "cU2HfOXj903ujd8g0wtATtLPVJSGxQcqs2QuWR9g9qBHiaN71Lta6IeYfpB132VI\n" +
                    "tqqSjKo/JDsz7tTdycTnBshkw0ypey//Yr2NT1eF4k60a5Fb89IUuO9uNe1CxM9R\n" +
                    "LUKArikCgYEA8/OuJkjlm/ER52KJuR12SU0Xb+RBqG42OcZ9AB5N5d613A0IAVJN\n" +
                    "WndlecAE2qSxW6yN6zeC/JYb08JyqzhmsvIXUPGVMomWEiAt26FACar72iSKyanW\n" +
                    "dSPtHXGAzTI030wRSx0yrX2AqV392IqhhX6/Qab4x7i84M5moqUdwIMCgYEA3wsl\n" +
                    "WXwfSLOpGbc5iNDXY8cfTuA5FlGKtJDRw3fWvgiocTnuAngN2EkAup0JV+vf7Q21\n" +
                    "Kkt9xjXIu2XNYxJjcSh5+3VI4KfAZkC2+q9AGQqFtgRggUE61K6XjJvvfrb/lxcZ\n" +
                    "R3Wp9MM078mxQmqm/1nLG7PIWqikc7K0BRfzclcCgYEAibg5yndA2KZhmyTv50eQ\n" +
                    "rZUN9CcCQhnmFvvNObHh/DGcrVvV9xkFflsFOJSEqQJs9foWLIIsOnhrwUJ3Bd5T\n" +
                    "bBZrv1CDEALIa/cqrGD+C/cxM1eJ2os8/OiJRiP7uNvtKVqCMMuM/pBpgDQFyKeK\n" +
                    "xBhn1KJ0tBvA1ZDwNEXO5A0CgYANC4DWt/y4atGUEHRL/TLrwgShv1l3ZbOAFgw+\n" +
                    "q26i1KzNWn8CI/VjVfm18GNQghopqiiTXbDwe8rOswiNJ9ErGBvySgzGHumQ0W4i\n" +
                    "QUe1bzkcySq/xInlUITKk4AxYBCYzKAM3uPezd/1uk0kvkogGydW6m/oZX6gya3f\n" +
                    "smPppwKBgC1mc6ozc/6OGXkIVsbCEhZvKyodPpp+DBzVNjJ/XwjNbGwFKTqeoBqe\n" +
                    "Bkc6MFGav3Ee7QVphvFjmzNP93qYZD5Cy206r0IDdpJb+V0O4YTTo8bq5gUGLyB2\n" +
                    "lWkfUlZj9xP/AV41R0J/qei664LKJGkmgsyD2vdRzB4YLV7JbvYW\n" +
                    "-----END RSA PRIVATE KEY-----\n";
            // 公钥版本号，判断消息能否解密
            Integer pubKeyVer = 7;

            log.info("读取配置文件  corpid:{}  secret:{}  priKey:{}", corpid, secret, priKey);
            Finance.Init(sdk, corpid, secret); // 初始化
            // seq 表示该企业存档消息序号，该序号单调递增，拉取序号建议设置为上次拉取返回结果中最大序号。
            // 首次拉取时seq传0，sdk会返回【有效期内】最早的消息。
            int seq = 0; // 从指定的seq开始拉取消息，注意的是返回的消息从seq+1开始返回，seq为之前接口返回的最大seq值。首次使用请使用seq:0（这个值需要记录下来，以便下一次的拉去）
            int limit = 1000;
            //创建切片
            long slice = Finance.NewSlice();
            List<WxMsg> errorMsg = CollectionUtil.newArrayList();
            List<WxMsg> successMsg = CollectionUtil.newArrayList();
            try {

                //外层循环
                outer:
                while (true){
                    WxChatResp wxChatResp = getWxChatResp(sdk, seq, limit, slice);
                    if (wxChatResp == null) return;
                    if (wxChatResp.getErrcode() != null){
                        log.warn("拉取消息失败:{}",JSONObject.toJSONString(wxChatResp));
                        return;
                    }
                    if (CollectionUtil.isEmpty(wxChatResp.getChatdata())) {
                        break outer;
                    }
                    long ret;
                    //log.info(seq + "，拉去的聊天记录密文结果：{}", JSONObject.toJSONString(wxChatResp));// 测试完成后去掉
                    //查询最后一条消息
                    WxMsg msg = wxMsgService.lambdaQuery().orderByDesc(WxMsg::getMsgtime).last("limit 1").one();
                    //倒序 从后往前遍历
                    Collections.reverse(wxChatResp.getChatdata());
                    boolean isFist = true;
                    //内层循环
                    inner:
                    for (Chatdata chatdata : wxChatResp.getChatdata()) {
                        if (isFist) {
                            seq = chatdata.getSeq();
                            isFist = false;
                        }
                        //log.info("开始循环处理，{}", JSONObject.toJSONString(chatdata));
                        //如果最后一条消息已经在库中 那么表示这批消息已经读取过了 读取下一批消息
                        if(ObjectUtil.isNotNull(msg)&&msg.getMsgid().equals(chatdata.getMsgid())){
                            //重新走外层循环
                            continue outer;
                        }
                        WxMsg wxmsg = new WxMsg();
                        BeanUtil.copyProperties(chatdata,wxmsg);
                        wxmsg.setSdk(sdk);
                        //公钥版本
                        Integer publicKeyVer = chatdata.getPublickeyVer();
                        if (ObjectUtil.notEqual(publicKeyVer, pubKeyVer)) {
                            log.warn("公钥版本不一致，无法解密当前消息，当前消息版本:{}   系统配置版本:{}", publicKeyVer, pubKeyVer);
                            String message = String.format("公钥版本不一致，无法解密当前消息，当前消息版本:%s   系统配置版本:%s", publicKeyVer,publicKeyVer);
                            wxmsg.setErrorCode(FAIL.getCode());
                            wxmsg.setErrorDesc(message);
                            errorMsg.add(wxmsg);
                            continue inner;
                        }
                        //加密密钥
                        String encryptRandomKey = chatdata.getEncryptRandomKey();
                        //加密聊天消息
                        String encryptChatMsg = chatdata.getEncryptChatMsg();
                        long msgid = Finance.NewSlice();
                        try {
                            // 获取加密密钥
                            String encryptKey = RSAEncrypt.decryptRSA(encryptRandomKey, priKey);
                            log.info("解析密文.企业微信自有解密内容");
                            // 解析密文.企业微信自有解密内容
                            ret = Finance.DecryptData(sdk, encryptKey, encryptChatMsg, msgid);
                            if (ret != 0) {
                                log.info("解密聊天记录失败 ret ：{}", ret);
                                wxmsg.setErrorCode(FAIL.getCode());
                                wxmsg.setErrorDesc(String.format("解密聊天记录失败 ret ：%s", ret));
                                errorMsg.add(wxmsg);
                                continue;
                            }
                            // 获取切片中的内容
                            String plaintext = Finance.GetContentFromSlice(msgid);
                            ChatMsg chatMsg = JSONObject.parseObject(plaintext, ChatMsg.class);
                           // log.info("解密结果：{}", JSONObject.toJSONString(chatMsg));
                            //如果是切换企业日志
                            if (SWITCH.getCode().equals(chatMsg.getAction())) {
                                wxmsg.setMsgtype(SWITCH.getCode());
                                wxmsg.setAction(SWITCH.getCode());
                                wxmsg.setMsgtime(LocalDateTimeUtil.of(chatMsg.getTime()));
                                wxmsg.setErrorCode(SUCCESS.getCode());
                                wxmsg.setMsgFrom(chatMsg.getUser());
                                successMsg.add(wxmsg);
                                continue;
                            }

                            // 释放slice
                            Finance.FreeSlice(msgid);
                            JSONObject plaintextJson = JSONObject.parseObject(plaintext);
                            // 文件类型  "text"文本 ,"revoke"撤回消息

                            String msgtype = chatMsg.getMsgtype();
                            wxmsg.setAction(chatMsg.getAction());
                            wxmsg.setContent(JSONObject.toJSONString(chatMsg.getMsgType(msgtype)));
                            wxmsg.setMsgtype(msgtype);
                            wxmsg.setSdk(sdk);
                            wxmsg.setErrorCode(SUCCESS.getCode());
                            wxmsg.setMsgFrom(chatMsg.getFrom());
                            wxmsg.setMsgtime(LocalDateTimeUtil.of(chatMsg.getMsgtime()));
                            wxmsg.setTolist(chatMsg.getTolist().stream().collect(Collectors.joining(";")));
                            wxmsg.setRoomid(chatMsg.getRoomid());
                            wxmsg.setSeq(chatdata.getSeq());
                            successMsg.add(wxmsg);
                            log.info("会话内容写入队列 ，存储消息，类型，时间，userid，等信息  :{}", JSONObject.toJSONString(wxmsg));
                        } catch (Exception e) {
                            log.error("循环拉会话异常：e:{}", e);
                            wxmsg.setErrorCode(FAIL.getCode());
                            wxmsg.setErrorDesc(ExceptionUtil.stacktraceToString(e));
                            errorMsg.add(wxmsg);
                        }
                    }
                }
                log.info("拉取数据结束");
            } catch (Exception e) {
                log.error("拉会话消息异常：e:{}", e);
            } finally {
                // 释放slice
                Finance.FreeSlice(slice);
                if(CollectionUtil.isNotEmpty(errorMsg)){
                    wxMsgService.saveBatch(errorMsg);
                }
                if(CollectionUtil.isNotEmpty(successMsg)){
                    if (wxMsgService.saveBatch(successMsg)) {
                        rabbitMQUtil.sendDelayMessage(WX_MEAAGES_HANDEL_EXCHANGE,WX_MEAAGES_HANDEL_KEY,successMsg,3 * 1000);
                    }
                }
            }
        }
    }


    /**
     * 拉取企微聊天记录
     * @param sdk
     * @param seq 序号
     * @param limit
     * @param slice
     * @return
     */
    private static WxChatResp getWxChatResp(long sdk, int seq, int limit, long slice) {
        //拉取聊天记录
        long ret = Finance.GetChatData(sdk, seq, limit, null, null, 5, slice);
        if (ret != 0) {
            log.info("拉取聊天记录失败 ret:{}", ret);
            return null;
        }
        //获取切片中的内容
        String contentFromSlice = Finance.GetContentFromSlice(slice);
        WxChatResp wxChatResp = JSONObject.parseObject(contentFromSlice, WxChatResp.class);
        return wxChatResp;
    }
}
